﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Data.Entity;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using System.Transactions;
using YWM.ContextPools;
using YWM.EntityFramework.Extensions;

namespace YWM.EntityFramework
{
    /// <summary>
    /// 数据库上下文池
    /// </summary>
    public class DbContextPool : IDbContextPool, IDisposable
    {
        /// <summary>
        /// 线程安全的数据库上下文集合
        /// </summary>
        private readonly ConcurrentDictionary<Guid, DbContext> _dbContexts;

        /// <summary>
        /// 构造函数
        /// </summary>
        public DbContextPool()
        {
            _dbContexts = new ConcurrentDictionary<Guid, DbContext>();
            foreach (Type value in Penetrates.DbContextDescriptors.Values)
            {
                DbContext dbContext = (DbContext)YApp.Resolve(value);
                AddToPool(dbContext);
            }
        }

        /// <summary>
        /// 数据库上下文事务
        /// </summary>
        public DbContextTransaction DbContextTransaction { get; private set; }

        /// <summary>
        /// 获取所有数据库上下文
        /// </summary>
        /// <returns></returns>
        public ConcurrentDictionary<Guid, DbContext> GetDbContexts()
        {
            return _dbContexts;
        }

        /// <summary>
        /// 保存数据库上下文
        /// </summary>
        /// <param name="dbContextObj"></param>
        private void AddToPool(DbContext dbContext)
        {
            Guid instanceId = Guid.NewGuid();
            _dbContexts.TryAdd(instanceId, dbContext);
        }

        /// <summary>
        /// 保存数据库上下文池中所有已更改的数据库上下文
        /// </summary>
        /// <returns></returns>
        public int SavePoolNow()
        {
            // 查找所有已改变的数据库上下文并保存更改
            return _dbContexts
                .Where(u => u.Value != null && !CheckDbContextDispose(u.Value) && u.Value.ChangeTracker.HasChanges())
                .Select(u => u.Value.SaveChanges()).Count();
        }

        /// <summary>
        /// 保存数据库上下文池中所有已更改的数据库上下文
        /// </summary>
        /// <param name="acceptAllChangesOnSuccess"></param>
        /// <returns></returns>
        public int SavePoolNow(bool acceptAllChangesOnSuccess)
        {
            // 查找所有已改变的数据库上下文并保存更改
            return _dbContexts
                .Where(u => u.Value != null && !CheckDbContextDispose(u.Value) && u.Value.ChangeTracker.HasChanges())
                .Select(u => u.Value.SaveChanges()).Count();
        }

        /// <summary>
        /// 保存数据库上下文池中所有已更改的数据库上下文
        /// </summary>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task<int> SavePoolNowAsync(CancellationToken cancellationToken = default)
        {
            // 查找所有已改变的数据库上下文并保存更改
            IEnumerable<Task<int>> tasks = _dbContexts
                .Where(u => u.Value != null && !CheckDbContextDispose(u.Value) && u.Value.ChangeTracker.HasChanges())
                .Select(u => u.Value.SaveChangesAsync(cancellationToken));

            // 等待所有异步完成
            int[] results = await Task.WhenAll(tasks);
            return results.Length;
        }

        /// <summary>
        /// 保存数据库上下文池中所有已更改的数据库上下文（异步）
        /// </summary>
        /// <param name="acceptAllChangesOnSuccess"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task<int> SavePoolNowAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
        {
            // 查找所有已改变的数据库上下文并保存更改
            IEnumerable<Task<int>> tasks = _dbContexts
                .Where(u => u.Value != null && !CheckDbContextDispose(u.Value) && u.Value.ChangeTracker.HasChanges())
                .Select(u => u.Value.SaveChangesAsync(cancellationToken));

            // 等待所有异步完成
            int[] results = await Task.WhenAll(tasks);
            return results.Length;
        }

        /// <summary>
        /// 打开事务
        /// </summary>
        /// <param name="ensureTransaction"></param>
        /// <returns></returns>
        public void BeginTransaction(bool ensureTransaction = false)
        {
            // 判断是否启用了分布式环境事务，如果是，则跳过
            if (Transaction.Current != null) return;

            // 判断 dbContextPool 中是否包含DbContext，如果是，则使用第一个数据库上下文开启事务，并应用于其他数据库上下文
            EnsureTransaction: if (_dbContexts.Any())
            {
                // 如果共享事务不为空，则直接共享
                if (DbContextTransaction != null) goto ShareTransaction;

                // 先判断是否已经有上下文开启了事务
                KeyValuePair<Guid, DbContext> transactionDbContext = _dbContexts.FirstOrDefault(u => u.Value.Database.CurrentTransaction != null);

                DbContextTransaction = transactionDbContext.Value != null
                       ? transactionDbContext.Value.Database.CurrentTransaction
                       // 如果没有任何上下文有事务，则将第一个开启事务
                       : _dbContexts.First().Value.Database.BeginTransaction();

            // 共享事务
            ShareTransaction: ShareTransaction(DbContextTransaction.UnderlyingTransaction);
            }
            else
            {
                // 判断是否确保事务强制可用（此处是无奈之举）
                if (!ensureTransaction) return;

                KeyValuePair<string, Type> defaultDbContextDescriptor = Penetrates.DbContextDescriptors.LastOrDefault();
                if (defaultDbContextDescriptor.Key == null) return;

                // 创建一个新的上下文
                DbContext newDbContext = (DbContext)YApp.Resolve(defaultDbContextDescriptor.Value);
                DbContextTransaction = newDbContext.Database.BeginTransaction();
                goto EnsureTransaction;
            }
        }

        /// <summary>
        /// 提交事务
        /// </summary>
        /// <param name="withCloseAll">是否自动关闭所有连接</param>
        public void CommitTransaction(bool withCloseAll = false)
        {
            // 判断是否启用了分布式环境事务，如果是，则跳过
            if (Transaction.Current != null) return;

            try
            {
                // 将所有数据库上下文修改 SaveChanges();，这里另外判断是否需要手动提交
                int hasChangesCount = SavePoolNow();

                // 如果事务为空，则执行完毕后关闭连接
                if (DbContextTransaction == null)
                {
                    if (withCloseAll) CloseAll();
                    return;
                }

                // 提交共享事务
                DbContextTransaction?.Commit();
            }
            catch
            {
                // 回滚事务
                if (DbContextTransaction?.UnderlyingTransaction?.Connection != null) DbContextTransaction?.Rollback();

                throw;
            }
            finally
            {
                if (DbContextTransaction?.UnderlyingTransaction?.Connection != null)
                {
                    DbContextTransaction = null;
                    DbContextTransaction?.Dispose();
                }
            }

            // 关闭所有连接
            if (withCloseAll) CloseAll();
        }

        /// <summary>
        /// 回滚事务
        /// </summary>
        /// <param name="withCloseAll">是否自动关闭所有连接</param>
        public void RollbackTransaction(bool withCloseAll = false)
        {
            // 判断是否启用了分布式环境事务，如果是，则跳过
            if (Transaction.Current != null) return;

            // 回滚事务
            if (DbContextTransaction?.UnderlyingTransaction?.Connection != null) DbContextTransaction?.Rollback();
            DbContextTransaction?.Dispose();
            DbContextTransaction = null;

            // 关闭所有连接
            if (withCloseAll) CloseAll();
        }

        /// <summary>
        /// 释放所有数据库上下文
        /// </summary>
        public void CloseAll()
        {
            if (!_dbContexts.Any()) return;

            foreach (DbContext item in _dbContexts.Values)
            {
                if (CheckDbContextDispose(item)) continue;

                DbConnection conn = item.Database.Connection;
                if (conn.State != ConnectionState.Open) continue;

                conn.Close();
            }
        }

        /// <summary>
        /// 设置数据库上下文共享事务
        /// </summary>
        /// <param name="transaction"></param>
        /// <returns></returns>
        private void ShareTransaction(DbTransaction transaction)
        {
            // 跳过第一个数据库上下文并设置共享事务
            _dbContexts
                    .Where(u => u.Value != null && !CheckDbContextDispose(u.Value) && ((dynamic)u.Value).UseUnitOfWork == true && u.Value.Database.CurrentTransaction == null)
                    .ToList()
                    .ForEach(u => u.Value.Database.UseTransaction(transaction));
        }

        /// <summary>
        /// 释放所有上下文
        /// </summary>
        public void Dispose()
        {
            _dbContexts.Clear();
        }

        /// <summary>
        /// 判断数据库上下文是否释放
        /// </summary>
        /// <param name="dbContext"></param>
        /// <returns></returns>
        private static bool CheckDbContextDispose(DbContext dbContext)
        {
            // 反射获取 _disposed 字段，判断数据库上下文是否已释放
            FieldInfo _disposedField = typeof(DbContext).GetField("_disposed", BindingFlags.Instance | BindingFlags.NonPublic);
            if (_disposedField == null) return false;

            bool _disposed = Convert.ToBoolean(_disposedField.GetValue(dbContext));
            return _disposed;
        }
    }
}